﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Media;
using System.Threading;
using System.Windows.Forms;
using AutoLoL.Entities;
using AutoLoL.Logic.RamGecTools;
using AutoLoL.Logic.Windows;
using AutoLoL.Shared;
using WindowsInput;
using WindowsInput.Native;

namespace AutoLoL.Logic.Automation
{
    public class AutoChat
    {
        private static Dictionary<KeyboardHook.VKeys, bool> _preventKeyStatus;
        private static bool _isModifierDown = false;
        private static bool _isMessageSending = false;
        private static ChatItem _selectedChatItem = null;
        private static DateTime _lastMessageSend = DateTime.Now;
        private static SoundPlayer _errorSound;
        private static KeyboardHook _keyboardHook;

        private static void Init()
        {
            // Load sound
            _errorSound = new SoundPlayer(Constants.ERROR_SOUND);
            _errorSound.LoadAsync();

            // Create and install hook
            _keyboardHook = new KeyboardHook();
            _keyboardHook.KeyDown += new KeyboardHook.KeyboardHookCallback(KeyboardHook_KeyDown);
            _keyboardHook.KeyUp += new KeyboardHook.KeyboardHookCallback(KeyboardHook_KeyUp);

            // If any of these keys (except the modifier key) are down AutoChat will refuse to work
            #region Prevent Keys
            _preventKeyStatus = new Dictionary<KeyboardHook.VKeys, bool>();
            _preventKeyStatus.Add(KeyboardHook.VKeys.SPACE, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.LMENU, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.MENU, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.RMENU, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.LCONTROL, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.CONTROL, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.RCONTROL, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.LSHIFT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.SHIFT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.RSHIFT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.LWIN, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.RWIN, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.CAPITAL, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.RETURN, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.LEFT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.RIGHT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.UP, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.DOWN, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.TAB, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.END, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.HOME, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.DELETE, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.INSERT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.PRIOR, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.NEXT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.BACK, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.NUMLOCK, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.SNAPSHOT, false);
            _preventKeyStatus.Add(KeyboardHook.VKeys.ESCAPE, false);
            #endregion
        }

        public static void StartListening()
        {
            if (_keyboardHook == null)
            {
                Init();
            }

            _keyboardHook.Install();
        }

        public static void StopListening()
        {
            if (_keyboardHook != null)
            {
                _keyboardHook.Uninstall();
            }
        }

        private static void KeyboardHook_KeyDown(KeyboardHook.VKeys key)
        {
            if ((int)key == UserSettings.Instance.AutoChatModifierKey)
            {
                // Mark the modifier key as pressed
                _isModifierDown = true;
            }
            else if (_preventKeyStatus.ContainsKey(key))
            {
                // Mark a prevention key as pressed
                _preventKeyStatus[key] = true;
            }

            // If the modifier key is down AND the right chat key is pressed
            if (!_isMessageSending && _isModifierDown && UserSettings.Instance.AutoChatKeys.Contains((int)key))
            {
                int autoChatItemIndex = Array.IndexOf(UserSettings.Instance.AutoChatKeys, (int)key) + 1;

                if (_selectedChatItem == null)
                {
                    // No item selected yet
                    _selectedChatItem = UserSettings.Instance.ChatItems.Where(c => c.Index == autoChatItemIndex).SingleOrDefault();
                }
                else if (_selectedChatItem.Children != null)
                {
                    // Select child
                    _selectedChatItem = _selectedChatItem.Children.Where(c => c.Index == autoChatItemIndex).SingleOrDefault();
                }
                else
                {
                    // Item does not contain children
                    _selectedChatItem = null;
                    _errorSound.Play();
                }
            }
        }

        private static void KeyboardHook_KeyUp(KeyboardHook.VKeys key)
        {
            // Mark a prevention key as released
            if (_preventKeyStatus.ContainsKey(key))
            {
                _preventKeyStatus[key] = false;
            }

            if ((int)key == UserSettings.Instance.AutoChatModifierKey)
            {
                try
                {
                    using (GameWindow window = new GameWindow())
                    {
                        if (window.IsForegroundWindow && _selectedChatItem != null)
                        {
                            // AutoLoL will send an error sound when:
                            // - A menu item is selected instead of a chat item
                            // - When a previous message was send less than 2 seconds ago
                            // - When a 'prevent key' is down
                            if (_selectedChatItem.Children != null
                                || (DateTime.Now - _lastMessageSend).TotalSeconds < Constants.AUTOCHAT_MIN_INTERVAL
                                || _preventKeyStatus.Values.Contains(true))
                            {
                                _errorSound.Play();
                            }
                            else
                            {
                                _lastMessageSend = DateTime.Now;
                                _isMessageSending = true;
                                SendMessage(_selectedChatItem.Title);
                                _isMessageSending = false;
                            }
                        }
                    }
                }
                catch { if (Debugger.IsAttached) throw; }

                // Deselect
                _isModifierDown = false;
                _selectedChatItem = null;
            }
        }

        // TODO - Check how this is handled on slower machines
        private const int CHUNK_SIZE = 15;

        public static void SendMessage(string text)
        {
            text = ParseMessage(text);

            KeyboardSimulator simulator = new KeyboardSimulator();
            simulator.KeyPress(VirtualKeyCode.RETURN);
            Thread.Sleep(200);

            int index = 0;
            while (index < text.Length)
            {
                int size = CHUNK_SIZE - Math.Max(index + CHUNK_SIZE - text.Length, 0);
                string print = text.Substring(index, size);
                simulator.TextEntry(print);
                index += CHUNK_SIZE;
                Thread.Sleep(100);
            }

            simulator.KeyPress(VirtualKeyCode.RETURN);
        }

        private static string ParseMessage(string text)
        {
            while (true)
            {
                int start, end;

                start = text.IndexOf("{");

                if (start > -1)
                {
                    end = text.IndexOf("}", start);

                    if (end > -1)
                    {
                        // Save the text
                        string before = text.Substring(0, start);
                        string after = text.Substring(end + 1);

                        //  Find the block
                        string block = text.Substring(start + 1, (end - start) - 1);

                        // Get the action and parameter
                        string action = null;
                        string parameter = null;

                        int pos = block.IndexOf(":");
                        if (pos > -1)
                        {
                            action = block.Substring(0, pos);
                            parameter = block.Substring(pos + 1);
                        }
                        else
                        {
                            action = block;
                        }

                        string result = string.Empty;

                        // Perform the action
                        switch (action)
                        {
                            // Inserts game time
                            case "t":
                                // TODO - This should be replaced with game time later
                                DateTime now = DateTime.Now;
                                if (parameter != null)
                                {
                                    int seconds = 0;
                                    if (int.TryParse(parameter, out seconds))
                                    {
                                        result = now.AddSeconds(seconds).ToString("HH:mm:ss");
                                    }
                                }
                                else
                                {
                                    result = now.ToString("HH:mm:ss");
                                }

                                break;
                            default:
                                break;
                        }

                        // Inser the result
                        text = before + result + after;

                        continue;
                    }
                }

                break;
            }

            return text;
        }
    }
}
